﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace PixelWarfare
{
    /// <summary>
    /// Field to store and manage multiple blobs.
    /// </summary>
    class BlobField
    {
        #region Fields

        private int width, height;              // Width and height of the field.
        private int totalActivePixels;          // Total number of pixels set to active.

        private int nextFlag;

        /* Contains the current flag of the blob that occupies each pixel
         * and whether the pixel is active. */
        private CoordData[,] points;            

        private Bitmap bmp;

        private List<Blob> blobList;            // List of all blobs in this field.

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a Blob Field with a given width and height.
        /// </summary>
        /// <param name="width">Width of the blob field.</param>
        /// <param name="height">Height of the blob field.</param>
        public BlobField(int width, int height)
        {
            this.width = width;
            this.height = height;

            totalActivePixels = width * height;

            nextFlag = 1; // Start at 1 since 0 represents unoccupied.

            blobList = new List<Blob>();

            bmp = new Bitmap(width, height);

            InitializePoints(); // Initializes and sets all the points to active and there flags to 0.
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Updates all the blobs in the field and if they are all finished then return true,
        /// otherwise return false.
        /// </summary>
        /// <returns>Returns true if all the blobs are finished, false otherwise.</returns>
        public bool Update()
        {
            int drawn = 0;      // Keeps track of the number of times each blob has drawn a pixel.
            int done = 0;       // Keeps track of how many blobs are done drawing.
            int finished = 0;   // Keeps track of how many blobs are completely finished.

            // Reset
            foreach (Blob blob in blobList)
            {
                blob.updated = false;
            }

                
            while (done < blobList.Count)
            {
                /* Go through all the blobs in order and update them each
                 * one pixel at a time to maintain fairness. */
                foreach (Blob blob in blobList)
                {
                    /* Only draw it until its all filled out or it has reached its 
                     * speed cap this allows some blobs to grow faster then others. */
                    if (blob.list.Count > 0 && drawn < blob.speed)
                    {
                        UpdateOne(blob);
                            
                    }
                    else if (blob.updated == false)
                    {
                        blob.updated = true;
                        done++;
                        // If its list is empty then its completely finished.
                        if (blob.list.Count == 0)
                        {
                            finished++;
                        }
                    }

                }
                drawn++;
                }

            // If all the blobs are finished.
            if (finished == blobList.Count)
            {
                return true;
            }
                /*
            // If there is only one blob left speed it up so the players dont have to wait as long.
            else if (finished == blobList.Count - 1)
            {
                foreach (Blob blob in blobList)
                {
                    if (blob.list.Count > 0)
                    {
                        blob.speed = width + height;
                        break;
                    }  
                }

            }*/
            return false;
        }

        /// <summary>
        /// Gets the bitmap of the field.
        /// </summary>
        /// <returns>The field bitmap.</returns>
        public Bitmap GetImage()
        {
            return bmp;
        }

        /// <summary>
        /// Add a blob to the field.
        /// </summary>
        /// <param name="x">Starting X coordinate.</param>
        /// <param name="y">Starting Y coordinate</param>
        /// <param name="factor">Factor of how it expands.</param>
        /// <param name="speed">Number of pixels it expands per update.</param>
        /// <param name="team">Team it belongs too.</param>
        /// <param name="color">The color of the blob.</param>
        public void AddBlob(int x, int y, int factor, int speed, int team, Color color)
        {
                Blob blob = new Blob();

                blob.start = PointToInt(x, y);          // Convert the coordinate to a single int.
                blob.flag = nextFlag++;                 // Use the next available flag.
                blob.team = team;
                blob.factor = ConvertFactor(factor);    // Factor needs to be converted into a actual value.
                blob.speed = Math.Max(1, speed);        // Speed must be atleast 1 or the field can freeze.
                blob.color = color;
                blob.size = 0;                          // Size always starts at 0.
                blob.list = new List<int>();
                blob.list.Add(blob.start);              // Add the starting point to the blobs list.

                blobList.Add(blob);                     // Actually add the blob.
        }

        /// <summary>
        /// Activates a given map by deactivating all the points of the given bitmap
        /// that aren't black (#00 00 00), this prevents blobs from accessing these pixels.
        /// </summary>
        /// <param name="map">Bitmap of the map to be added.</param>
        public void ActivateMap(Bitmap map)
        {
            totalActivePixels = map.Width * map.Height;

            if (totalActivePixels != width * height)
            {
                return;
            }

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    int rgb = map.GetPixel(x, y).R + map.GetPixel(x, y).G + map.GetPixel(x, y).B;

                    // If the color of the pixel is not black (plus a little slack.)
                    if (rgb > 30)
                    {
                        points[x, y].active = false;
                        totalActivePixels--; // If the point is deactivated then there is one less active pixel.
                    }
                    else
                    {
                        points[x, y].active = true;
                    }
                }
            }
        }

        /// <summary>
        /// Sets all the pixels to active.
        /// </summary>
        public void RemoveMap()
        {
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    points[x, y].active = true;
                }
            }
        }

        /// <summary>
        /// Returns whether the given coordinate is active.
        /// </summary>
        /// <param name="x">X coordinate.</param>
        /// <param name="y">Y coordinate.</param>
        /// <returns>Returns whether the point is active</returns>
        public bool IsActivePoint(int x, int y)
        {
            return points[x, y].active;
        }

        /// <summary>
        /// Removes all blobs from the field.
        /// </summary>
        public void FlushBlobs()
        {
            blobList = new List<Blob>();
            nextFlag = 1;
        }

        /// <summary>
        /// Clear the field and add the blobs starting points to their list again
        /// so they are ready to spread.
        /// </summary>
        /// <param name="resetBitmap">Optional choice to reset the picture as well.</param>
        public void Restart(bool resetBitmap = true)
        {
            ClearField(resetBitmap);

            foreach (Blob s in blobList)
            {
                s.list.Clear();
                s.list.Add(s.start);
            }
        }

        /// <summary>
        /// Gets the percentage of the field the given team has filled.
        /// </summary>
        /// <param name="team">The team to check.</param>
        /// <returns>The percentage.</returns>
        public double GetTeamPercentage(int team)
        {
            double result = 0;

            foreach (Blob b in blobList)
            {
                // Add the sizes together.
                if (b.team == team)
                {
                    result += b.size;
                }
            }

            // Return the percentage.
            return result / totalActivePixels;
        }

        /// <summary>
        /// Get the percentage of the field the given blob has filled.
        /// The first blob added is 1, then 2, 3 etc.
        /// If there is no blob by that number then -1 is returned.
        /// </summary>
        /// <param name="blobNumber">The number cooresponding to the blob size you want.</param>
        /// <returns>The percentage.</returns>
        public double GetBlobPercentage(int team, int blobNumber)
        {
            int count = 0;

            foreach (Blob b in blobList)
            {
                if (b.team == team)
                {
                    if (count == blobNumber)
                    {
                        return (b.size + 0.0) / totalActivePixels;
                    }
                    count++;
                }        
            }

            return -1;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Update one pixel of the given blob.
        /// </summary>
        /// <param name="blob">Blob to update.</param>
        private void UpdateOne(Blob blob) 
        {
            RandomOrder rand = new RandomOrder(4);
            bool darken = false;
            int temp;

            /* A factor of 0 means pick from the list completely at random the next point to spread to.
             * A positive factor means take from the upper or most recently added (1/factor) of the list 
             * which resembles depth first search more and more as the factor increases.
             * Simalarily, a negative factor means take from the lower or least recently added (1/factor)
             * of the list which resembles breadth first search more and more as the factor gets lower. */

            if (blob.factor > 0)
                temp = (int)(blob.list.Count * (blob.factor - 1) / blob.factor + new Random().Next((int)(blob.list.Count / blob.factor)));
            else
            {
                temp = (int)(new Random().Next(blob.list.Count) / (-1 * blob.factor));
            }

            if (temp < 0 || temp >= blob.list.Count)
                temp = 0;

            // Get the point your gonna expand to.
            int point = blob.list.ElementAt(temp);                          
            blob.list.Remove(point);
            int curx = point % (width);                 
            int cury = point / (width);

            blob.size++;    // Blob has grown by one pixel.
            
            // In a random order add each direction to the blobs list if its not already filled.
            for (int i = 0; i < 4; i++)
            {      
                switch ((Direction)rand.Next())
                {
                    case Direction.Up:
                        // If its active and the blob doesnt already occupy it.
                        if (cury - 1 >= 0 && points[curx, cury - 1].active && points[curx, cury - 1].flag != blob.flag)
                        {
                            // Only if its not filled.
                            if (points[curx, cury - 1].flag == 0)
                            {
                                points[curx, cury - 1].flag = blob.flag;
                                blob.list.Add(point - width);
                            }
                            // Set the pixel to be darker if its bordering another blob.
                            else if (points[curx, cury - 1].flag != blob.flag)
                            {
                                darken = true;
                            }
                        }
                        break;
                    case Direction.Down:
                        if (cury + 1 < height && points[curx, cury + 1].active && points[curx, cury + 1].flag != blob.flag)
                        {
                            if (points[curx, cury + 1].flag == 0)
                            {
                                points[curx, cury + 1].flag = blob.flag;
                                blob.list.Add(point + width);
                            }
                            else if (points[curx, cury + 1].flag != blob.flag)
                            {
                                darken = true;
                            }
                        }
                        break;
                    case Direction.Right:
                        if (curx + 1 < width && points[curx + 1, cury].active && points[curx + 1, cury].flag != blob.flag)
                        {
                            if (points[curx + 1, cury].flag == 0)
                            {
                                points[curx + 1, cury].flag = blob.flag;
                                blob.list.Add(point + 1);
                            }
                            else if (points[curx + 1, cury].flag != blob.flag)
                            {
                                darken = true;
                            }
                        }
                        break;
                    case Direction.Left:
                        if (curx - 1 >= 0 && points[curx - 1, cury].active && points[curx - 1, cury].flag != blob.flag)
                        {
                            if (points[curx - 1, cury].flag == 0)
                            {
                                points[curx - 1, cury].flag = blob.flag;
                                blob.list.Add(point - 1);
                            }
                            else if (points[curx - 1, cury].flag != blob.flag)
                            {
                                darken = true;
                            }
                        }
                        break;
                }
            }

            // Set the pixel to the blobs colour, or darken it if its on a border.
            if (darken)
            {
                bmp.SetPixel(curx, cury, Utility.Darken(blob.color, 50));
            }
            else
            {
                bmp.SetPixel(curx, cury, blob.color);
            }
                


        }

        /// <summary>
        /// Convert a user entered factor into a more usuable value.
        /// </summary>
        /// <param name="newFactor">Factor to convert.</param>
        /// <returns>Converted factor.</returns>
        private int ConvertFactor(int newFactor)
        {
            if (newFactor > 0)
            {
                return (int)Math.Pow(2, newFactor);
            }
            else if (newFactor < 0)
            {
                return (int)(-1 * Math.Pow(2, Math.Abs(newFactor)));
            }
            else
            {
                return 1;
            }
        }

        /// <summary>
        /// Set all the flags back to 0 and refresh the bitmap to all black 
        /// if the resetBitmap is true.
        /// </summary>
        /// <param name="resetBitmap">Whether to reset the bitmap.</param>
        private void ClearField(bool resetBitmap)
        {
            if (resetBitmap)
            {
                bmp = new Bitmap(width, height);
            }

            for (int i = 0; i < width; i++)
            {
                for (int u = 0; u < height; u++)
                {
                        points[i, u].flag = 0;
                }
            }

        }

        /// <summary>
        /// Consolidates a coordinate to a single integer.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        private int PointToInt(int x, int y)
        {
            return y * width + x;
        }

        /// <summary>
        /// Initialize all the points in the field.
        /// </summary>
        private void InitializePoints()
        {
            points = new CoordData[width, height];

            for (int i = 0; i < width; i++)
            {
                for (int u = 0; u < height; u++)
                {
                    points[i, u] = new CoordData();   
                    points[i, u].active = true;
                    points[i, u].flag = 0;
                }
            }
        }

        #endregion

        #region Private Types

        private enum Direction
        {
            Up,
            Right,
            Down,
            Left
        }

        /// <summary>
        /// Used to represent a blob in the field.
        /// A blob is just a source to spread from
        /// with a speed and colour that it fills with.
        /// </summary>
        private class Blob
        {
            public int start;       // Source coordinate.
            public int flag;        // All blobs must have different flags.
            public int team;
            public int factor;      // Factor with which it spreads.
            public int speed;
            public int size;        // Number of pixels it has currently filled.
            public bool updated;
            public Color color;
            public List<int> list;
        }

        /// <summary>
        /// Used to keep track of the state of each pixel.
        /// Whether they are active and which blob occupies them.
        /// </summary>
        private struct CoordData
        {
            public bool active;
            public int flag;
        }

        #endregion
    }
}
